import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from layers.Embed import DataEmbedding
from layers.Conv_Blocks import Inception_Block_V1

# 由Claude修改
## 主要修复的ONNX兼容性问题：
# 1. 移除Python控制流
# 原代码中的 for 循环被向量化操作替代
# 使用矩阵运算计算所有频率的DFT，避免动态循环

# 2. 避免复数类型
# ONNX不完全支持复数，改用实部和虚部分别计算
# 使用 torch.sqrt(real²+imag²) 计算幅度

# 3. 消除NumPy依赖
# 移除了 .detach().cpu().numpy() 操作
# 保持所有计算在PyTorch张量上

# 4. 修复动态形状问题
# 使用 F.pad 替代 torch.cat 进行填充
# 预计算目标长度，减少条件分支

# 5. 优化索引操作
# 使用 period_list[i].item() 将张量转为Python标量
# 使用 view 和 expand 替代 unsqueeze 和 repeat


def DFT_for_Period(x, k=2):
    """
    使用DFT替代FFT计算周期信息 
    """
    B, T, C = x.shape

    freq_len = T // 2 + 1
    n_indices = torch.arange(T, dtype=torch.float32, device=x.device).unsqueeze(0)  # [1, T]
    freq_indices = torch.arange(freq_len, dtype=torch.float32, device=x.device).unsqueeze(1)  # [freq_len, 1]
    
    omega_matrix = -2.0 * math.pi * freq_indices * n_indices / T  # [freq_len, T]
    
    cos_matrix = torch.cos(omega_matrix)  # [freq_len, T]
    sin_matrix = torch.sin(omega_matrix)  # [freq_len, T]
    x_transposed = x.transpose(1, 2)  # [B, C, T]
    
    real_part = torch.matmul(x_transposed, cos_matrix.T)  # [B, C, freq_len]
    imag_part = -torch.matmul(x_transposed, sin_matrix.T)  # [B, C, freq_len]
    
    amplitude = torch.sqrt(real_part * real_part + imag_part * imag_part)  # [B, C, freq_len]
    amplitude = amplitude.transpose(1, 2)  # [B, freq_len, C]
    
    frequency_list = amplitude.mean(0).mean(-1)  # [freq_len]
    frequency_list = frequency_list.clone()  # 确保可导
    frequency_list[0] = 0  # 忽略直流分量
    
    _, top_list = torch.topk(frequency_list, k)
    
    period = T / (top_list.float() + 1e-8)  # 添加小值避免除零
    period = period.long()  # 转换为长整型
    
    batch_indices = torch.arange(B, device=x.device).unsqueeze(1)  # [B, 1]
    top_list_expanded = top_list.unsqueeze(0).expand(B, -1)  # [B, k]
    period_weight = amplitude[batch_indices, top_list_expanded]  # [B, k, C]
    period_weight = period_weight.mean(-1)  # [B, k]
    
    return period, period_weight


class TimesBlock(nn.Module):
    def __init__(self, configs):
        super(TimesBlock, self).__init__()
        self.seq_len = configs.seq_len
        self.pred_len = configs.pred_len
        self.k = configs.top_k
        # parameter-efficient design
        self.conv = nn.Sequential(
            Inception_Block_V1(configs.d_model, configs.d_ff,
                               num_kernels=configs.num_kernels),
            nn.GELU(),
            Inception_Block_V1(configs.d_ff, configs.d_model,
                               num_kernels=configs.num_kernels)
        )

    def forward(self, x):
        B, T, N = x.size()
        period_list, period_weight = DFT_for_Period(x, self.k)

        # 预先计算所有可能需要的长度和填充，避免条件分支
        target_length = self.seq_len + self.pred_len
        
        # 使用张量化操作处理所有周期
        res_list = []
        for i in range(self.k):
            period = period_list[i].item()  # 转换为Python int以避免ONNX问题
            
            # 计算需要的长度
            if target_length % period != 0:
                length = ((target_length // period) + 1) * period
                padding_size = length - target_length
                # 使用torch.nn.functional.pad避免torch.cat的动态性
                out = F.pad(x, (0, 0, 0, padding_size), mode='constant', value=0.0)
            else:
                length = target_length
                out = x
            
            # reshape操作
            out = out.reshape(B, length // period, period, N).permute(0, 3, 1, 2).contiguous()
            
            # 2D conv
            out = self.conv(out)
            
            # reshape back
            out = out.permute(0, 2, 3, 1).reshape(B, -1, N)
            
            # 截取到目标长度
            out = out[:, :target_length, :]
            res_list.append(out)
        
        # 堆叠所有结果
        res = torch.stack(res_list, dim=-1)  # [B, T, N, k]
        
        # adaptive aggregation
        period_weight = F.softmax(period_weight, dim=1)  # [B, k]
        period_weight = period_weight.view(B, 1, 1, self.k).expand(B, T, N, self.k)
        
        # 加权求和
        res = torch.sum(res * period_weight, dim=-1)  # [B, T, N]
        
        # residual connection
        res = res + x
        return res


class Model(nn.Module):
    """
    Paper link: https://openreview.net/pdf?id=ju_Uqw384Oq
    """

    def __init__(self, configs):
        super(Model, self).__init__()
        self.configs = configs
        self.task_name = configs.task_name
        self.seq_len = configs.seq_len
        self.label_len = configs.label_len
        self.pred_len = configs.pred_len
        self.model = nn.ModuleList([TimesBlock(configs)
                                    for _ in range(configs.e_layers)])
        self.enc_embedding = DataEmbedding(configs.enc_in, configs.d_model, configs.embed, configs.freq,
                                           configs.dropout)
        self.layer = configs.e_layers
        self.layer_norm = nn.LayerNorm(configs.d_model)
        if self.task_name == 'long_term_forecast' or self.task_name == 'short_term_forecast':
            self.predict_linear = nn.Linear(
                self.seq_len, self.pred_len + self.seq_len)
            self.projection = nn.Linear(
                configs.d_model, configs.c_out, bias=True)
        if self.task_name == 'imputation' or self.task_name in ['anomaly_detection','anomaly_detection_v2']:
            self.projection = nn.Linear(
                configs.d_model, configs.c_out, bias=True)
        if self.task_name == 'classification':
            self.act = F.gelu
            self.dropout = nn.Dropout(configs.dropout)
            self.projection = nn.Linear(
                configs.d_model * configs.seq_len, configs.num_class)

    def forecast(self, x_enc, x_mark_enc, x_dec, x_mark_dec):
        # Normalization from Non-stationary Transformer
        means = x_enc.mean(1, keepdim=True).detach()
        x_enc = x_enc.sub(means)
        stdev = torch.sqrt(
            torch.var(x_enc, dim=1, keepdim=True, unbiased=False) + 1e-5)
        x_enc = x_enc.div(stdev)

        # embedding
        enc_out = self.enc_embedding(x_enc, x_mark_enc)  # [B,T,C]
        enc_out = self.predict_linear(enc_out.permute(0, 2, 1)).permute(
            0, 2, 1)  # align temporal dimension
        # TimesNet
        for i in range(self.layer):
            enc_out = self.layer_norm(self.model[i](enc_out))
        # project back
        dec_out = self.projection(enc_out)

        # De-Normalization from Non-stationary Transformer
        dec_out = dec_out.mul(
                  (stdev[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        dec_out = dec_out.add(
                  (means[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        return dec_out

    def imputation(self, x_enc, x_mark_enc, x_dec, x_mark_dec, mask):
        # Normalization from Non-stationary Transformer
        means = torch.sum(x_enc, dim=1) / torch.sum(mask == 1, dim=1)
        means = means.unsqueeze(1).detach()
        x_enc = x_enc.sub(means)
        x_enc = x_enc.masked_fill(mask == 0, 0)
        stdev = torch.sqrt(torch.sum(x_enc * x_enc, dim=1) /
                           torch.sum(mask == 1, dim=1) + 1e-5)
        stdev = stdev.unsqueeze(1).detach()
        x_enc = x_enc.div(stdev)

        # embedding
        enc_out = self.enc_embedding(x_enc, x_mark_enc)  # [B,T,C]
        # TimesNet
        for i in range(self.layer):
            enc_out = self.layer_norm(self.model[i](enc_out))
        # project back
        dec_out = self.projection(enc_out)

        # De-Normalization from Non-stationary Transformer
        dec_out = dec_out.mul(
                  (stdev[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        dec_out = dec_out.add(
                  (means[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        return dec_out

    def anomaly_detection(self, x_enc):
        # Normalization from Non-stationary Transformer
        means = x_enc.mean(1, keepdim=True).detach()
        x_enc = x_enc.sub(means)
        stdev = torch.sqrt(
            torch.var(x_enc, dim=1, keepdim=True, unbiased=False) + 1e-5)
        x_enc = x_enc.div(stdev)

        # embedding
        enc_out = self.enc_embedding(x_enc, None)  # [B,T,C]
        # TimesNet
        for i in range(self.layer):
            enc_out = self.layer_norm(self.model[i](enc_out))
        # project back
        dec_out = self.projection(enc_out)

        # De-Normalization from Non-stationary Transformer
        dec_out = dec_out.mul(
                  (stdev[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        dec_out = dec_out.add(
                  (means[:, 0, :].unsqueeze(1).repeat(
                      1, self.pred_len + self.seq_len, 1)))
        return dec_out

    def classification(self, x_enc, x_mark_enc):
        # embedding
        enc_out = self.enc_embedding(x_enc, None)  # [B,T,C]
        # TimesNet
        for i in range(self.layer):
            enc_out = self.layer_norm(self.model[i](enc_out))

        # Output
        # the output transformer encoder/decoder embeddings don't include non-linearity
        output = self.act(enc_out)
        output = self.dropout(output)
        # zero-out padding embeddings
        output = output * x_mark_enc.unsqueeze(-1)
        # (batch_size, seq_length * d_model)
        output = output.reshape(output.shape[0], -1)
        output = self.projection(output)  # (batch_size, num_classes)
        return output

    def forward(self, x_enc, x_mark_enc, x_dec, x_mark_dec, mask=None):
        if self.task_name == 'long_term_forecast' or self.task_name == 'short_term_forecast':
            dec_out = self.forecast(x_enc, x_mark_enc, x_dec, x_mark_dec)
            return dec_out[:, -self.pred_len:, :]  # [B, L, D]
        if self.task_name == 'imputation':
            dec_out = self.imputation(
                x_enc, x_mark_enc, x_dec, x_mark_dec, mask)
            return dec_out  # [B, L, D]
        if self.task_name == 'anomaly_detection':
            dec_out = self.anomaly_detection(x_enc)
            return dec_out  # [B, L, D]
        if self.task_name == 'classification':
            dec_out = self.classification(x_enc, x_mark_enc)
            return dec_out  # [B, N]
        return None